/*
 *	MidiConverter.java
 *
 *	This file is part of jsresources.org
 */

/*
 * Copyright (c) 1999 by Matthias Pfisterer
 * Copyright (c) 2003 by Florian Bomers
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
|<---            this code is formatted to fit into 80 columns             --->|
*/

import java.io.File;
import java.io.IOException;

import javax.sound.midi.Sequence;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiFileFormat;
import javax.sound.midi.Track;
import javax.sound.midi.InvalidMidiDataException;



/**	<titleabbrev>MidiConverter</titleabbrev>
	<title>Converting MIDI type 1 files to MIDI type 0 files</title>

	<formalpara><title>Purpose</title>
	<para>This program can convert MIDI type 1 files to MIDI type 0 files.
	It has two modes: single mode and multi mode.</para>

	<para>In single mode (default),
	all events of all tracks are
	assembled in one track which is saved as a single file.</para>

	<para>In multi mode (selected by <option>-m</option>),
	each track of the input file is separated and
	saved to the output file. In single mode,
	the input file will be overwritten if no output file name is provided.
	In multi mode, the track number is
	appended to the basename of the input file (i.e. it is inserted
	before the extension).</para>
	</formalpara>

	<formalpara><title>Usage</title>
	<para>
	<cmdsynopsis><command>java MidiConverter</command>
	<arg><option>-m</option></arg>
	<arg choice="plain"><replaceable class="parameter">input_midifile</replaceable></arg>
	<arg choice="plain"><replaceable class="parameter">output_midifile</replaceable></arg>
	</cmdsynopsis>
	</para></formalpara>

	<formalpara><title>Parameters</title>
	<variablelist>
	<varlistentry>
	<term><option>-m</option></term>
	<listitem><para>selects multi mode</para></listitem>
	</varlistentry>
	<varlistentry>
	<term><replaceable class="parameter">input_midifile</replaceable></term>
	<listitem><para>the name of the MIDI file to process</para></listitem>
	</varlistentry>
	<varlistentry>
	<term><replaceable class="parameter">output_midifile</replaceable></term>
	<listitem><para>the name of the output MIDI file (optional)</para></listitem>
	</varlistentry>
	</variablelist>
	</formalpara>

	<formalpara><title>Bugs, limitations</title>
	<para>Not well tested.</para>
	</formalpara>

	<formalpara><title>Source code</title>
	<para>
	<ulink url="MidiConverter.java.html">MidiConverter.java</ulink>
	</para>
	</formalpara>

*/
// TODO: add an option '-T x' to transpose (be sure not to modifiy channel 10!)
public class MidiConverter
{
	public static void main(String[] args)
	{
		if (args.length < 1 || args.length > 3 || args[0].equals("-h"))
		{
			printUsageAndExit();
		}
		/*
		 *	This variable says whether we should process in
		 *	multi mode (each track is saved in an own file)
		 *	or in single mode (all tracks are united to one
		 *	track and saved in a single file).
		 */
		boolean		bUseMultiMode = false;
		int inFilenameIndex = 0;
		if (args[0].equals("-m"))
		{
			bUseMultiMode = true;
			inFilenameIndex++;
			if (args.length <= inFilenameIndex)
			{
				out("You need to specify an input file name!");
				printUsageAndExit();
			}
		}

 		String	strInFilename = args[inFilenameIndex];
 		File	inFile = new File(strInFilename);
		if (!inFile.exists())
		{
			out("The input file '"+strInFilename+"' does not exist!");
			printUsageAndExit();
		}

 		String	strOutFilename = strInFilename;
 		if (args.length > (inFilenameIndex + 1)) 
 		{
			strOutFilename = args[inFilenameIndex + 1];
		}
			
		
		/* verify that the input file is indeed type 1 */
		MidiFileFormat mff = null;
		try
		{
			mff = MidiSystem.getMidiFileFormat(inFile);
		}
		catch (InvalidMidiDataException e)
		{
			e.printStackTrace();
			System.exit(1);
		}
		catch (IOException e)
		{
			e.printStackTrace();
			System.exit(1);
		}
		if (mff.getType() == 0)
		{
			out("The input file you specified is already a type 0 MIDI file.");
			System.exit(1);
		}
		
		
		Sequence	sequence = null;
		try
		{
			sequence = MidiSystem.getSequence(inFile);
		}
		catch (InvalidMidiDataException e)
		{
			e.printStackTrace();
			System.exit(1);
		}
		catch (IOException e)
		{
			e.printStackTrace();
			System.exit(1);
		}
		Track[]		aTracks = sequence.getTracks();
		
		if (aTracks.length == 0)
		{
			out("The input file you specified does not contain any tracks! exit.");
			System.exit(1);
		}

		float		fDivisionType = sequence.getDivisionType();
		int		nResolution = sequence.getResolution();
		if (bUseMultiMode)
		{
			// for each input track, create a new sequence and copy all 
			// events of the input track to a new track in the new sequence
			// then save the new sequence as a distinct file
			for (int nTrack = 0; nTrack < aTracks.length; nTrack++)
			{
				Sequence	singleTrackSequence = null;
				try
				{
					singleTrackSequence = new Sequence(fDivisionType, 
					                                   nResolution);
				}
				catch (InvalidMidiDataException e)
				{
					e.printStackTrace();
					System.exit(1);
				}
				Track	track = singleTrackSequence.createTrack();
				for (int i = 0; i < aTracks[nTrack].size(); i++)
				{
					track.add(aTracks[nTrack].get(i));
				}
				int	nDotPosition = strOutFilename.lastIndexOf('.');
				String	strSingleTrackFilename = null;
				if (nDotPosition == -1)
				{
					strSingleTrackFilename = strOutFilename.substring(0, nDotPosition) 
					                         + "-" + nTrack;
				}
				else
				{
					strSingleTrackFilename = 
					    strOutFilename.substring(0, nDotPosition) 
					    + "-" 
					    + nTrack 
					    + strOutFilename.substring(nDotPosition);
				}

				try
				{
					int written = MidiSystem.write(singleTrackSequence, 0, new File(strSingleTrackFilename));
					out("Wrote "+strSingleTrackFilename+" ("+written+" bytes).");
				}
				catch (IOException e)
				{
					e.printStackTrace();
					System.exit(1);
				}
			}
		}
		else // single mode
		{
			Track firstTrack = aTracks[0];
			int nTrack;
			
			for (nTrack = 1; nTrack < aTracks.length; nTrack++)
			{
				Track	track = aTracks[nTrack];
				
				// add all events of this track to the first track
				for (int i = 0; i < track.size(); i++)
				{
					firstTrack.add(track.get(i));
				}
				
				// delete this track from the sequence
				sequence.deleteTrack(track);
			}
			
			// write out the new sequence
			try
			{
				int written = MidiSystem.write(sequence, 0, new File(strOutFilename));
				out("Wrote "+strOutFilename+" successfully ("+written+" bytes).");
			}
			catch (IOException e)
			{
				e.printStackTrace();
				System.exit(1);
			}
		}

	}

	private static void printUsageAndExit() {
		out("usage:");
		out("java MidiConverter  [-m]  <midifile> [outputfile]");
		System.exit(1);
	}


	private static void out(String strMessage)
	{
		System.out.println(strMessage);
	}
}



/*** MidiConverter.java ***/
